package com.google.sky.domain;

import com.google.common.collect.Sets;
import com.google.sky.Board;
import com.google.sky.Position;
import com.google.sky.dto.CellState;
import com.google.sky.excepts.EndOfGameException;
import com.google.sky.excepts.OutOfBoardException;
import com.google.sky.register.GameStateRegister;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Set;

/**
 * @author: sk_y
 */
public class BoardImpl implements Board, MovableBoard {
    private final Mark[][] board;
    private final int w;
    private final int h;
    private boolean isFinished = false;
    private Set<CellState> cellStates = Sets.newCopyOnWriteArraySet();


    private GameStateRegister gameStateRegister;

    public BoardImpl(int width, int height, GameStateRegister register) {
        board = new Mark[width][height];
        this.gameStateRegister = register;
        w = width;
        h = height;
    }

    @Override
    public Position getPositionAt(int x, int y) {
        if (x < 0 || x >= w || y < 0 || y >= h) {
            throw new OutOfBoardException(String.format("Position {%d,%d} is out of Board [%d,%d]", x, y, w, h));
        }
        return new PositionImpl(x, y, board[x][y] == null, board[x][y] == gameStateRegister.getMoveOwner());
    }

    @Override
    public int getWidth() {
        return w;
    }

    @Override
    public int getHeight() {
        return h;
    }

    public synchronized boolean isFinished() {
        return isFinished;
    }

    public synchronized boolean isFull(){
        return cellStates.size()==w*h;
    }


    @Override
    public synchronized void makeMove(Position position, Mark mark) {
        if (isFull()){
            throw new EndOfGameException(true);
        }
        if (board[position.getX()][position.getY()]!=null)
        {
            isFinished=true;
            throw new EndOfGameException(mark);
        }
        board[position.getX()][position.getY()] = mark;
        determinateFininshed(position.getX(), position.getY());
        cellStates.add(CellState.create(position.getX(), position.getY(), mark.toString()));
    }

    private synchronized  void determinateFininshed(int x, int y) {
        isFinished = checkVertical(x, y) || checkHorizontal(x, y) || checkUpRightDiagonal(x, y) || checkUpLeftDiagonal(x, y);
    }



    private boolean checkUpRightDiagonal(int x, int y) {
        if (isFinished()){
            return true;
        }
        Mark sign = board[x][y];
        int deltaXStart = x < 4 ? x : 4;
        int deltaYEnd = y + 4 >= h ? h - y - 1 : 4;
        int minStart = Math.min(deltaXStart, deltaYEnd);

        int deltaYStart = y < 4 ? y : 4;
        int deltaXEnd = x + 4 >= w ? w - x - 1 : 4;

        int minEnd = Math.min(deltaXEnd, deltaYStart);

        StringBuilder res = new StringBuilder();
        for (int i = x - minStart, j = y + minStart; i <= x + minEnd && j >= y - minEnd; i++, j--) {
            res.append(getMarkstring(board[i][j]));
        }
        return res.toString().contains(sign.getToFind());
    }

    private boolean checkUpLeftDiagonal(int x, int y) {
        if (isFinished()){
            return true;
        }
        Mark sign = board[x][y];
        int deltaXStart = x < 4 ? x : 4;
        int deltaYStart = y < 4 ? y : 4;
        int minStart = Math.min(deltaXStart, deltaYStart);
        int deltaXEnd = x + 4 >= w ? w - x - 1 : 4;
        int deltaYEnd = y + 4 >= h ? h - y - 1 : 4;
        int minEnd = Math.min(deltaXEnd, deltaYEnd);
        StringBuilder res = new StringBuilder();
        for (int i = x - minStart, j = y - minStart; i <= x + minEnd && j <= y + minEnd; i++, j++) {
            res.append(getMarkstring(board[i][j]));
        }
        return res.toString().contains(sign.getToFind());
    }

    private boolean checkVertical(int x, int y) {
        if (isFinished()){
            return true;
        }
        Mark sign = board[x][y];
        int startFrom = y - 4 < 0 ? 0 : y - 4;
        int endPosition = (y + 4 >= h) ? h - 1 : y + 4;
        StringBuilder horizontalLine = new StringBuilder();
        for (int i = startFrom; i <= endPosition; i++) {
            horizontalLine.append(getMarkstring(board[x][i]));
        }
        return horizontalLine.toString().contains(sign.getToFind());
    }

    private boolean checkHorizontal(int x, int y) {
        if (isFinished()){
            return true;
        }
        Mark sign = board[x][y];
        int startFrom = x - 4 < 0 ? 0 : x - 4;
        int endPosition = (x + 4 >= w) ? w - 1 : x + 4;
        StringBuilder horizontalLine = new StringBuilder();
        for (int i = startFrom; i <= endPosition; i++) {
            horizontalLine.append(getMarkstring(board[i][y]));
        }
        return horizontalLine.toString().contains(sign.getToFind());
    }

    private String getMarkstring(Mark mark) {
        return mark == null ? " " : mark.getStringMark();
    }

    public void setGameStateRegister(GameStateRegister gameStateRegister) {
        this.gameStateRegister = gameStateRegister;
    }

    public Set<CellState> getCellStates() {
        return cellStates;
    }
}
